module;
#include <QObject>
#include <QOpcUaClient>
#include <QOpcUaNode>
#include <QMap>
#include <QList>
#include <QString>
#include <QVariant>
#include <QDebug>
#include <QOpcUaProvider>
#include <QtCore/qtmochelpers.h>
module XYopcUa;

OpcUaClient::OpcUaClient(QObject *parent)
    : QObject(parent), m_opcuaClient(nullptr), m_node(nullptr), 
      m_currentNodeId(), m_pendingWrites(), m_writeResults(), m_batchNodes()
{

}

OpcUaClient::~OpcUaClient()
{
    // Disconnect all signals
    if (m_opcuaClient->state() != QOpcUaClient::ClientState::Disconnected) {
        disconnectFromEndpoint();
        delete m_opcuaClient;;
        m_opcuaClient = nullptr;
    }
    // Clean up single node
    if (m_node) {
        m_node->deleteLater();
        m_node = nullptr;
    }

    // Clean up batch nodes
    for (auto node : m_batchNodes) {
        if (node) {
            node->deleteLater();
        }
    }
    m_batchNodes.clear();

    // Clear pending operations
    m_pendingWrites.clear();
    m_writeResults.clear();
}

bool OpcUaClient::connectToEndpoint(const QString &address, quint16 port)
{
    // Initialize with default provider (open62541)
    QOpcUaProvider provider;
    m_opcuaClient = provider.createClient("open62541");

    if (!m_opcuaClient) {
        qCritical() << "Failed to create OPC UA client";
        emit errorOccurred(tr("Failed to initialize OPC UA client"));
        return false;
    }
    qDebug()<<"正在连接";
    // Configure client settings

    // Connect signals
    connect(m_opcuaClient, &QOpcUaClient::stateChanged,
            this, &OpcUaClient::onStateChanged);
    /*connect(m_opcuaClient.data(), &QOpcUaClient::errorChanged,
            this, &OpcUaClient::onErrorChanged);
    connect(m_opcuaClient.data(), &QOpcUaClient::endpointDiscovered,
            this, &OpcUaClient::onEndpointDiscovered)*/;
    if (m_opcuaClient->state() != QOpcUaClient::ClientState::Disconnected) {
        emit errorOccurred(tr("Client not initialized"));
        return false;
    }

    QOpcUaEndpointDescription endpoint;
    endpoint.setEndpointUrl(QString("opc.tcp://%1:%2").arg(address).arg(port));
    m_opcuaClient->connectToEndpoint(endpoint);
    return true;
}

void OpcUaClient::disconnectFromEndpoint()
{
    if (m_opcuaClient->state() != QOpcUaClient::ClientState::Disconnected) {
        m_opcuaClient->disconnectFromEndpoint();
    }
}

void OpcUaClient::readNode(const QString &nodeId)
{
    if (m_opcuaClient->state() != QOpcUaClient::ClientState::Connected) {
        emit errorOccurred(tr("Client not connected"));
        return;
    }

    m_currentNodeId = nodeId;
    m_node = m_opcuaClient->node(nodeId);
    if (m_node) {
        connect(m_node, &QOpcUaNode::attributeRead, this, &OpcUaClient::onReadFinished);
    } else {
        emit errorOccurred(tr("Failed to create node"));
        return;
    }
    m_node->readAttributes(QOpcUa::NodeAttribute::Value);
}

void OpcUaClient::readNodes(const QStringList &nodeIds)
{
    if (m_opcuaClient->state() != QOpcUaClient::ClientState::Connected) {
        emit errorOccurred(tr("Client not connected"));
        return;
    }

    auto results = QSharedPointer<QMap<QString, QVariant>>::create();
    auto nodeCount = nodeIds.size();
    for (const QString &nodeId : nodeIds) {
        QOpcUaNode *node = m_opcuaClient->node(nodeId);
        connect(node, &QOpcUaNode::attributeRead, this,
                [this, nodeId, results, nodeCount, node](QOpcUa::NodeAttributes attr) mutable {
                    if (attr & QOpcUa::NodeAttribute::Value) {
                        QVariant value = node->attribute(QOpcUa::NodeAttribute::Value);
                        if (value.isValid()) {
                            (*results)[nodeId] = value;
                        }
                        node->deleteLater();

                        if (results->size() == nodeCount) {
                            emit nodesRead(*results);
                        }
                    }
                }
                );
        node->readAttributes(QOpcUa::NodeAttribute::Value);
    }
}

void OpcUaClient::writeNode(const QString &nodeId, const QVariant &value)
{
    if (m_opcuaClient->state() != QOpcUaClient::ClientState::Connected) {
        emit errorOccurred(tr("Client not connected"));
        return;
    }

    m_currentNodeId = nodeId;
    m_node = m_opcuaClient->node(nodeId);
    connect(m_node, &QOpcUaNode::attributeWritten, this, &OpcUaClient::onWriteFinished);
    m_node->writeAttribute(QOpcUa::NodeAttribute::Value, value);
}

void OpcUaClient::writeNodes(const QMap<QString, QVariant> &nodes)
{
    if (m_opcuaClient->state() != QOpcUaClient::ClientState::Connected) {
        emit errorOccurred(tr("Client not connected"));
        return;
    }

    m_pendingWrites = nodes;
    m_writeResults.clear();

    for (auto it = nodes.begin(); it != nodes.end(); ++it) {
        QOpcUaNode *node = m_opcuaClient->node(it.key());
        connect(node, &QOpcUaNode::attributeWritten, this, &OpcUaClient::onBatchWriteFinished);
        node->writeAttribute(QOpcUa::NodeAttribute::Value, it.value());
        m_batchNodes.append(node);
    }
}

void OpcUaClient::onStateChanged(QOpcUaClient::ClientState state)
{
    switch (state) {
    case QOpcUaClient::ClientState::Connected:
        emit connected();
        break;
    case QOpcUaClient::ClientState::Disconnected:
        emit disconnected();
        break;
    default:
        break;
    }
}

void OpcUaClient::onReadFinished(QOpcUa::NodeAttributes attr)
{
    if (attr & QOpcUa::NodeAttribute::Value) {
        emit nodeValueRead(m_currentNodeId, m_node->attribute(QOpcUa::NodeAttribute::Value));
    }
    m_node->deleteLater();
}

void OpcUaClient::onBatchWriteFinished(QOpcUa::NodeAttribute attr, QOpcUa::UaStatusCode status)
{
    if (attr == QOpcUa::NodeAttribute::Value) {
        QOpcUaNode *node = qobject_cast<QOpcUaNode*>(sender());
        if (node) {
            QString nodeId = node->nodeId();
            m_writeResults[nodeId] = (status == QOpcUa::UaStatusCode::Good);
            m_pendingWrites.remove(nodeId);
            node->deleteLater();
            m_batchNodes.removeOne(node);

            if (m_pendingWrites.isEmpty()) {
                emit nodesWritten(m_writeResults);
            }
        }
    }
}

void OpcUaClient::onWriteFinished(QOpcUa::NodeAttribute attr, QOpcUa::UaStatusCode status)
{
    if (attr == QOpcUa::NodeAttribute::Value) {
        emit nodeValueWritten(m_currentNodeId, status == QOpcUa::UaStatusCode::Good);
    }
    m_node->deleteLater();
}

void OpcUaClient::connectToEndpointSlot(const QString &address, quint16 port)
{
    connectToEndpoint(address, port);
}

void OpcUaClient::disconnectFromEndpointSlot()
{
    disconnectFromEndpoint();
}

void OpcUaClient::readNodeSlot(const QString &nodeId)
{
    readNode(nodeId);
}

void OpcUaClient::readNodesSlot(const QStringList &nodeIds)
{
    readNodes(nodeIds);
}

void OpcUaClient::writeNodeSlot(const QString &nodeId, const QVariant &value)
{
    writeNode(nodeId, value);
}

void OpcUaClient::writeNodesSlot(const QMap<QString, QVariant> &nodes)
{
    writeNodes(nodes);
}

//这个要在最后一行 手动生成
#include "XYopcUa.moc"
